﻿using System;
using System.Configuration.Provider;
using System.Text.RegularExpressions;
using System.Web.Security;
using Less.Core.Model;
using Less.SRV;
using Less.Tools;
using Omu.ValueInjecter;

namespace Less.Web.Security
{

	/// <summary>
	/// An implementation of a membershipprovider using lessDataBase
	/// </summary>
	public class LessMembershipProvider : MembershipProvider
	{

		private const string EmailValidationPattern = @"^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$";
		readonly SecuritySRV _securityService = new SecuritySRV();

		#region Overrides of MembershipProvider

		public override MembershipUser CreateUser(
			string username, string password, string email, 
			string passwordQuestion, string passwordAnswer, 
			bool isApproved, object providerUserKey,out MembershipCreateStatus status)
		{
			// Validate arguments
			if (username == null) throw new ArgumentNullException("username");
			if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException(Msg.ValueCannotBeEmpty(), "username");
			if (password == null) throw new ArgumentNullException("password");
			if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException(Msg.ValueCannotBeEmpty(), "password");
			// Validate and normalize username
			username = username.ToLower().Trim();
			if (username.Length > 100)
			{
				status = MembershipCreateStatus.InvalidUserName;
				return null;
			}
			// Validate password (local)
			if (password.Length < Membership.MinRequiredPasswordLength)
			{
				status = MembershipCreateStatus.InvalidPassword;
				return null;
			}
			// Validate password (external)
			var args = new ValidatePasswordEventArgs(username, password, true);
			this.OnValidatingPassword(args);
			if (args.Cancel)
			{
				if (args.FailureInformation != null) throw args.FailureInformation;
				status = MembershipCreateStatus.InvalidPassword;
				return null;
			}
			// Validate  e-mail address
			if (this.RequiresUniqueEmail && string.IsNullOrWhiteSpace(email))
			{
				// E-mail is not specified when provider requires unique e-mail addresses
				status = MembershipCreateStatus.InvalidEmail;
				return null;
			}
			if (!string.IsNullOrWhiteSpace(email))
			{
				email = email.ToLower().Trim();
				if (!Regex.IsMatch(email, EmailValidationPattern))
				{
					// Invalid e-mail syntax
					status = MembershipCreateStatus.InvalidEmail;
					return null;
				}
				if (this.RequiresUniqueEmail && !string.IsNullOrEmpty(this.GetUserNameByEmail(email)))
				{
					// Duplicate e-mail
					status = MembershipCreateStatus.DuplicateEmail;
					return null;
				}
			}
			// Check if we don't already have such user
			if (this.GetUser(username, false) != null)
			{
				status = MembershipCreateStatus.DuplicateUserName;
				return null;
			}
			var membershipUser = new MembershipUser("LessMembershipProvider", username, null, email, 
				passwordQuestion, "", false, false,
				DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now);
			var user = new User();
			user.InjectFrom(membershipUser);
			user.FailedPasswordAnswerAttemptCount = 0;
			user.FailedPasswordAnswerAttemptWindowStart = DateTime.Now;
			user.FailedPasswordAttemptCount = 0;
			user.FailedPasswordAttemptWindowStart = DateTime.Now;
			user.Identifier = Guid.NewGuid().ToString();
			user.Password = password;
			_securityService.InsertUser(user);
			status = MembershipCreateStatus.Success;
			return membershipUser;
		}

		

		public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
		{
			var user = _securityService.SelectByName(username);
			if (user != null && user.Password == password)
			{
				user.PasswordQuestion = newPasswordQuestion;
				user.PasswordAnswer = newPasswordAnswer;
				_securityService.UpdateUser(user);
			}
			return false;
		}

		public override string GetPassword(string username, string answer)
		{
			string password = _securityService.GetPassword(username, answer);
			return password;
		}

		public override bool ChangePassword(string username, string oldPassword, string newPassword)
		{
			var user = _securityService.SelectByName(username);
			if (user.Password == oldPassword)
			{
				user.Password = newPassword;
				user.LastPasswordChangedDate = DateTime.Now;
				_securityService.UpdateUser(user);
				return true;
			}
			return false;
		}

		public override string ResetPassword(string username, string answer)
		{
			var user = _securityService.SelectByName(username);
			if (user != null && user.PasswordAnswer == answer) return user.Password;
			return "";
		}

		public override void UpdateUser(MembershipUser membershipUser)
		{
			if (membershipUser != null && membershipUser.UserName != null)
			{
				var user = _securityService.SelectByName(membershipUser.UserName);
				user.InjectFrom(membershipUser);
				_securityService.UpdateUser(user);
			}
		}

		public override bool ValidateUser(string username, string password)
		{
			// Validate arguments
			if (username == null) throw new ArgumentNullException("username");
			if (string.IsNullOrWhiteSpace(username)) throw new ArgumentException(Msg.ValueCannotBeEmpty(), "username");
			if (password == null) throw new ArgumentNullException("password");
			if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException(Msg.ValueCannotBeEmpty(), "password");
			// Normalize arguments
			username = username.ToLower().Trim();
			// Query database
			bool userValid = false;
			try
			{
				var user = _securityService.SelectByName(username);
				if (user != null)
				{
					if (user.Password != null && user.Password == password)
					{
						user.LastLoginDate = DateTime.Now;
						user.LastActivityDate = DateTime.Now;
						_securityService.UpdateUser(user);
						userValid = true;
					}
				}
				else
				{
					Log.Warning(string.Format("User {0} no found", username));
				}
			}
			catch (Exception ex)
			{
				throw new ProviderException(Msg.ErrorExecutingQuery(), ex);
			}
			return userValid;
		}

        public  bool ValidateUser(string uid)
        {
            // Validate arguments
            if ( uid == null ) throw new ArgumentNullException("identifier");
            if ( string.IsNullOrWhiteSpace(uid) ) throw new ArgumentException(Msg.ValueCannotBeEmpty(), "identifier");
            // Normalize arguments
            uid = uid.Trim();
            // Query database
            bool userValid = false;
            try
            {
                var user = GetUserByUID(uid);
                if ( user != null )
                {
                    user.LastLoginDate = DateTime.Now;
                    user.LastActivityDate = DateTime.Now;
                    _securityService.UpdateUser(user);
                    userValid = true;
                }
                else
                {
                    Log.Warning(string.Format("User {0} no found", uid));
                }
            }
            catch ( Exception ex )
            {
                throw new ProviderException(Msg.ErrorExecutingQuery(), ex);
            }
            return userValid;
        }

        public User GetUserByUID(string uid)
        {
            var identifier = _securityService.SelectIdentifierByGUID(uid);
            var user = _securityService.SelectById(identifier.UserId);
            return user;
        }


		public override bool UnlockUser(string userName)
		{
			var user = _securityService.SelectByName(userName);
			user.IsLockedOut = false;
			_securityService.UpdateUser(user);
			return true;
		}

		public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
		{
			var user = _securityService.SelectById((int)providerUserKey);
			if (user != null)
			{
				if (userIsOnline)
				{
					user.LastActivityDate = DateTime.Now;
					_securityService.UpdateUser(user);
				}
				var membershipUser = ConvertMembershipUserFromUser(user);
				return membershipUser;
			}
			else return null;
		}

		public override MembershipUser GetUser(string username, bool userIsOnline)
		{
			var user = _securityService.SelectByName(username);
			if (user != null)
			{
				if (userIsOnline)
				{
					user.LastActivityDate = DateTime.Now;
					_securityService.UpdateUser(user);
				}
				var membershipUser = ConvertMembershipUserFromUser(user);
				return membershipUser;
			}
			else return null;


		}

		private static MembershipUser ConvertMembershipUserFromUser(User user)
		{
			var membershipUser = new MembershipUser("LessMembershipProvider", user.UserName, user.Id, user.Email, "", user.Comment, user.IsApproved, user.IsLockedOut, user.CreationDate, user.LastLoginDate, user.LastActivityDate, user.LastPasswordChangedDate, user.LastLockoutDate);
			return membershipUser;
		}

		public override string GetUserNameByEmail(string email)
		{
			var user = _securityService.SelectByEmail(email);
			return user != null ? user.UserName : "";
		}

		public override bool DeleteUser(string username, bool deleteAllRelatedData)
		{
			var user = _securityService.SelectByName(username);
			if (user != null)
			{
				_securityService.DeleteUser(user);
				return true;
			}
			return false;
		}

		public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
		{
			MembershipUserCollection userCollection = null;
			totalRecords = 0;
			var users = _securityService.GetAllUsers(pageIndex, pageSize, out totalRecords);
			if (users.HasValue())
			{
				userCollection = new MembershipUserCollection();
				foreach (User user in users)
				{
					MembershipUser membershipUser = ConvertMembershipUserFromUser(user);
					userCollection.Add(membershipUser);
				}
				totalRecords = users.Count;
			}
			return userCollection;
		}

		public override int GetNumberOfUsersOnline()
		{
			return _securityService.GetNumberOfUsersOnline(DateTime.Now.AddMinutes(-Membership.UserIsOnlineTimeWindow));
		}

		public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
		{
			throw new NotImplementedException();
		}

		public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
		{
			throw new NotImplementedException();
		}

		// The following properties are inherited from MembershipProvider and fully supported

		private int _minRequiredNonAlphanumericCharacters = 0;

		public override int MinRequiredNonAlphanumericCharacters
		{
			get
			{
				return _minRequiredNonAlphanumericCharacters;
			}
		}

		private int _minRequiredPasswordLength = 6;
		public override int MinRequiredPasswordLength
		{
			get
			{
				return this._minRequiredPasswordLength;
			}
		}

		private readonly string _passwordStrengthRegularExpression = null;
		public override string PasswordStrengthRegularExpression
		{
			get
			{
				return this._passwordStrengthRegularExpression;
			}
		}

		private bool _requiresUniqueEmail = true;
		public override bool RequiresUniqueEmail
		{
			get
			{
				return this._requiresUniqueEmail;
			}
		}

		public override string ApplicationName { get; set; }

		// The following properties are inherited from MembershipProvider and fixed by provider implementation

		public override bool EnablePasswordReset
		{
			get { return true; }
		}

		public override bool EnablePasswordRetrieval
		{
			get { return false; }
		}

		public override MembershipPasswordFormat PasswordFormat
		{
			get { return MembershipPasswordFormat.Hashed; }
		}

		public override bool RequiresQuestionAndAnswer
		{
			get { return false; }
		}

		// The following properties are inherited from MembershipProvider and are not implemented

		public override int MaxInvalidPasswordAttempts
		{
			get { throw new NotImplementedException("Account lockdown is intentionally not implemented due to possibility of DoS attack."); }
		}

		public override int PasswordAttemptWindow
		{
			get { throw new NotImplementedException("Account lockdown is intentionally not implemented due to possibility of DoS attack."); }
		}

		#endregion
	}
}